#pragma once

#include "indicator_common.h"

namespace mdtx::online::indicator
{

    // s2 related indicators

    class zscore
    {
    private:
        boost::circular_buffer<double> dtbuf;
        mdtx::online::updater::s2_updater<MDTX_INDICATOR_KAHAN> dts2;
        double z, r;

        double insert(double x)
        {
            double newpt, signal, sd;
            if (dtbuf.full())
            {
                sd = std::sqrt(dts2.s2 / (dts2.n - 1));
                if (std::abs(x - dts2.m) > z * sd)
                {
                    newpt = r * x + (1.0 - r) * dtbuf.front();
                    signal = (x - dts2.m) / sd;
                }
                else
                {
                    newpt = x;
                    signal = 0.0;
                }
                dts2.remove(dtbuf.back());
            }
            else
            {
                newpt = x;
                signal = 0.0;
            }
            dtbuf.push_front(newpt);
            dts2.insert(newpt);
            return signal;
        }

    public:
        zscore(int period, double z, double r)
            : dtbuf(period),
              dts2(),
              z(0.0),
              r(0.0)
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt first, InputIt last, OutputIt output)
        {
            while (first != last)
            {
                *output++ = insert(*first++);
            }
        }

        void operator()(double input, double &output)
        {
            output = insert(input);
        }
    };

    class volatility
    {
    private:
        double ann;
        boost::circular_buffer<double> pbuf;
        mdtx::online::updater::s2_updater<MDTX_INDICATOR_KAHAN> s2;

        double insert(double x)
        {
            double p, val;
            if (pbuf.size())
            {
                p = x / pbuf.front() - 1.0;
                val = ann * std::sqrt(s2.s2 / s2.n);
            }
            else
            {
                p = 0.0;
                val = 0.0;
            }
            s2.insert(p);
            if (pbuf.full())
            {
                s2.remove(pbuf.back());
            }
            pbuf.push_front(p);

            return val;
        }

    public:
        volatility(int period, int annualise = 250)
            : ann(std::sqrt(annualise)),
              pbuf(period),
              s2()
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt first, InputIt last, OutputIt output)
        {
            while (first != last)
            {
                *output++ = insert(*first++);
            }
        }

        void operator()(double input, double &output)
        {
            output = insert(input);
        }
    };

    class variance
    {
        boost::circular_buffer<double> xbuf;
        mdtx::online::updater::s2_updater<MDTX_INDICATOR_KAHAN> s2;

        double insert(double x)
        {
            s2.insert(x);
            if (xbuf.full())
            {
                s2.remove(xbuf.back());
            }
            xbuf.push_front(x);
            return s2.s2 / s2.n;
        }

    public:
        explicit variance(int period)
            : xbuf(period),
              s2()
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt first, InputIt last, OutputIt output)
        {
            while (first != last)
            {
                *output++ = insert(*first++);
            }
        }

        void operator()(double input, double &output)
        {
            output = insert(input);
        }
    };

    class bband
    {
    private:
        boost::circular_buffer<double> xbuf;
        mdtx::online::updater::s2_updater<MDTX_INDICATOR_KAHAN> s2;
        double z, band;

        void insert(double x)
        {
            s2.insert(x);
            if (xbuf.full())
            {
                s2.remove(xbuf.back());
            }
            xbuf.push_front(x);
            band = z * std::sqrt(s2.s2 / s2.n);
        }

    public:
        bband(int period, double z)
            : xbuf(period),
              s2(),
              z(z),
              band(std::numeric_limits<double>::max())
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt first, InputIt last, OutputIt upper, OutputIt lower)
        {
            while (first != last)
            {
                insert(*first++);
                *upper++ = s2.m + band;
                *lower++ = s2.m - band;
            }
        }

        void operator()(double input, double &upper, double &lower)
        {
            insert(input);
            upper = s2.m + band;
            lower = s2.m - band;
        }
    };

    class zbband
    {
    private:
        boost::circular_buffer<double> dtbuf;
        mdtx::online::updater::s2_updater<MDTX_INDICATOR_KAHAN> dts2;
        double z, r, r0, band;

        void insert(double x)
        {
            double newpt;
            if (std::abs(x - dts2.m) > band)
            {
                newpt = r * x + r0 * dtbuf.front();
            }
            else
            {
                newpt = x;
            }
            dts2.insert(newpt);
            if (dtbuf.full())
            {
                dts2.remove(dtbuf.back());
            }
            dtbuf.push_front(newpt);
            band = z * std::sqrt(dts2.s2 / dts2.n);
        }

    public:
        zbband(int period, double z, double r)
            : dtbuf(period),
              dts2(),
              z(z),
              r(r),
              r0(1.0 - r),
              band(std::numeric_limits<double>::max())
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt first, InputIt last, OutputIt upper, OutputIt lower)
        {
            while (first != last)
            {
                insert(*first++);
                *upper++ = dts2.m + band;
                *lower++ = dts2.m - band;
            }
        }

        void operator()(double input, double &upper, double &lower)
        {
            insert(input);
            upper = dts2.m + band;
            lower = dts2.m - band;
        }
    };

    class ebband
    {
    private:
        mdtx::online::updater::s2ew_updater<MDTX_INDICATOR_KAHAN> s2;
        double z, band;

        void insert(double x)
        {
            s2.insert(x);
            band = z * std::sqrt(s2.s2);
        }

    public:
        ebband(int period, double z)
            : s2(2.0 / (period + 1)),
              z(z)
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt first, InputIt last, OutputIt upper, OutputIt lower)
        {
            while (first != last)
            {
                insert(*first++);
                *upper++ = s2.m + band;
                *lower++ = s2.m - band;
            }
        }

        void operator()(double input, double &upper, double &lower)
        {
            insert(input);
            upper = s2.m + band;
            lower = s2.m - band;
        }
    };

}
